<?php

namespace tmcore\services\addons\module;

use app\common\cache\AdminAuthCache;
use think\facade\Db;
use tmcore\exceptions\AddonsException;
use tmcore\services\addons\AddonApiService;
use tmcore\services\addons\BaseService;
use think\Exception;
use tmcore\services\addons\ModelService;
use tmcore\services\addons\UtilService;
use tmcore\utils\File;

class Install extends BaseService
{
    protected string $addon_key;

    protected string $addon_cache;

    public function __construct(string $addon = '')
    {
        parent::__construct();
        $this->addon_key = $addon;
        $this->addon_cache = 'addon_'. $this->addon_key. '_install';
    }

    /**
     * 安装前检验
     * @param $params
     * @return true
     * @throws AddonsException
     * @throws Exception
     */
    protected function check($params)
    {
        if (!is_dir($this->sys_path . 'admin' . DIRECTORY_SEPARATOR)) throw new Exception('后台 admin 目录不存在');
        if (!is_dir($this->sys_path . 'uniapp' . DIRECTORY_SEPARATOR)) throw new Exception('手机端 uniapp 目录不存在');
        if (!is_dir($this->sys_path . 'pc' . DIRECTORY_SEPARATOR)) throw new Exception('电脑端 pc 目录不存在');

        // 检验目录
        $addons_admin_dir = $this->sys_path . 'admin/src/addons/' . $this->addon_key . DIRECTORY_SEPARATOR;
        $addons_app_dir = $this->sys_path . 'uniapp/src/addons/' . $this->addon_key . DIRECTORY_SEPARATOR;
        $addons_pc_dir = $this->sys_path . 'pc/addons/' . $this->addon_key . DIRECTORY_SEPARATOR;
        $addons_server_dir = $this->addon_path . $this->addon_key . DIRECTORY_SEPARATOR;
        $addons_resource_dir = $this->root_path . 'public/addons/' . $this->addon_key . DIRECTORY_SEPARATOR;

        $dir_arr = [$addons_admin_dir, $addons_app_dir, $addons_pc_dir, $addons_server_dir, $addons_resource_dir];

        foreach ($dir_arr as $dir) {
            if(!is_dir($dir)){
                File::mk_dir($dir, 0755);
            } else {
                if(!File::is_write($dir)) throw new AddonsException( str_replace($this->sys_path, '', $dir) . ' 目录不可写');
            }
        }

        // 检验预下载插件文件
        $addons_download_dir = $this->download_path . $this->addon_key . DIRECTORY_SEPARATOR;
        if(!$addons_download_dir) throw new AddonsException('插件文件未下载');

        $addon_file = $addons_download_dir . 'server/Addon.php';
        if(!is_file($addon_file)) throw new AddonsException('插件类文件缺失');

        $info_file = $addons_download_dir . 'server/info.php';
        if(!is_file($info_file)) throw new AddonsException('插件配置文件缺失');

        // 核对版本信息
        $tempArr = (array) include $info_file;
        if(!(isset($params['identify']) && $tempArr['name'] == $params['identify'])) throw new AddonsException('插件标识不匹配');
        if(!(isset($params['version']) && $tempArr['version'] == $params['version'])) throw new AddonsException('插件版本不匹配');
        if(isset($tempArr['addon_require']) && $tempArr['addon_require']) $this->checkAddonInstallRequire($tempArr['addon_require']);
        return true;
    }

    /**
     * 检查插件是否已安装
     * @return bool
     */
    protected function checkAddonInstall()
    {
        // 数据检验
        $info = (new ModelService)->getInfoByKey($this->addon_key);
        return empty($info);
    }

    /**
     * 检查插件安装依赖
     * @param $addon_list
     * @return bool
     * @throws AddonsException
     */
    protected function checkAddonInstallRequire($addon_list)
    {
        if($addon_list && is_array($addon_list)){
            foreach ($addon_list as $key => $value){
                $info = (new ModelService)->getInfoByKey($key);
                if(empty($info)) {
                    throw new AddonsException('插件' . $key .'依赖缺失，不可安装');
                } else {
                    // 比较版本
                    if(version_compare($info['version'], $value) < 0) {
                        throw new AddonsException('插件' . $key .'依赖版本过低，不可安装');
                    }
                }
            }
        }

        return true;
    }


    /**
     * 安装目录
     * @return true
     */
    protected function installDir()
    {
        // 下载目录
        $download_admin_dir = $this->download_path . $this->addon_key . '/admin' . DIRECTORY_SEPARATOR;
        $download_app_dir = $this->download_path . $this->addon_key . '/uniapp' . DIRECTORY_SEPARATOR;
        $download_pc_dir = $this->download_path . $this->addon_key . '/pc' . DIRECTORY_SEPARATOR;
        $download_server_dir = $this->download_path . $this->addon_key . '/server' . DIRECTORY_SEPARATOR;
        $download_resource_dir = $this->download_path . $this->addon_key . '/resource' . DIRECTORY_SEPARATOR;

        // 转移目录
        $addon_admin_dir = $this->sys_path . 'admin/src/addons/' . $this->addon_key . DIRECTORY_SEPARATOR;
        $addon_app_dir = $this->sys_path . 'uniapp/src/addons/' . $this->addon_key . DIRECTORY_SEPARATOR;
        $addon_pc_dir = $this->sys_path . 'pc/addons/' . $this->addon_key . DIRECTORY_SEPARATOR;
        $addon_server_dir = $this->addon_path . $this->addon_key . DIRECTORY_SEPARATOR;
        $addon_resource_dir = $this->root_path . 'public/addons/' . $this->addon_key . DIRECTORY_SEPARATOR;

        // 安装后台管理端
        if (file_exists($download_admin_dir)) {
            File::copy_dir($download_admin_dir, $addon_admin_dir);
        }

        // 安装手机端
        if (file_exists($download_app_dir)) {
            File::copy_dir($download_app_dir, $addon_app_dir);
        }

        // 安装电脑端
        if (file_exists($download_pc_dir)) {
            File::copy_dir($download_pc_dir, $addon_pc_dir);
        }

        // 安装服务端
        if (file_exists($download_server_dir)) {
            File::copy_dir($download_server_dir, $addon_server_dir);
        }

        // 安装资源文件
        if (file_exists($download_resource_dir)) {
            File::copy_dir($download_resource_dir, $addon_resource_dir);
        }

        return true;
    }

    /**
     * 安装依赖
     * @return true
     * @throws Exception
     */
    protected function installDepend()
    {
        $Depend = (new Depend);

        // composer 依赖更新
        $sys_composer_content = $Depend->getSysComposerContent();
        $addon_composer_content = $Depend->getAddonComposerContent($this->addon_key);
        if($addon_composer_content) {
            if (isset($addon_composer_content['require'])) {
                $sys_composer_content['require'] = array_merge($sys_composer_content['require'], $addon_composer_content['require']);
            }
            if (isset($addon_composer_content['require-dev'])) {
                $sys_composer_content['require-dev'] = array_merge($sys_composer_content['require-dev'], $addon_composer_content['require-dev']);
            }
            $Depend->setSysComposerContent($sys_composer_content);
        }

        foreach (['admin', 'uniapp', 'pc'] as $item) {
            // npm 依赖更新
            $sys_npm_content = $Depend->getSysNpmContent($item);
            $addon_npm_content = $Depend->getAddonNpmContent($this->addon_key, $item);
            if($addon_npm_content) {
                if (isset($addon_npm_content['dependencies'])) {
                    $sys_npm_content['dependencies'] = array_merge($sys_npm_content['dependencies'], $addon_npm_content['dependencies']);
                }
                if (isset($addon_npm_content['devDependencies'])) {
                    $sys_npm_content['devDependencies'] = array_merge($sys_npm_content['devDependencies'], $addon_npm_content['devDependencies']);
                }
                $Depend->setSysNpmContent($sys_npm_content, $item);
            }
        }

        // pages.json 更新
        $sys_page_content = $Depend->getSysPageContent();
        $addon_page_content = $Depend->getAddonPageContent($this->addon_key);
        if($addon_page_content) {
            if (isset($addon_page_content['pages'])) {
                // 路径相同不操作
                foreach ($sys_page_content['pages'] as $key => $item) {
                    foreach ($addon_page_content['pages'] as $k => $addon_item) {
                        if(isset($addon_item['path']) && $item['path'] == $addon_item['path']) {
                            unset($addon_page_content['pages'][$k]);
                        }
                    }
                }
                // 路径不同合并
                foreach ($addon_page_content['pages'] as $k => $addon_item) {
                    if(isset($addon_item['path'])) {
                        $sys_page_content['pages'][] = $addon_item;
                    }
                }
                $Depend->setSysPageContent($sys_page_content);
            }
        }

        return true;
    }

    /**
     * 备份目录
     * @return true
     */
    protected function backupDir()
    {
        $backup_dir = $this->root_path . 'data/backup' . DIRECTORY_SEPARATOR;
        if(is_dir($backup_dir)) {
            File::del_dir($backup_dir);
        } else {
            File::mk_dir($backup_dir);
        }

        // 循环备份前端目录
        foreach (['admin', 'app', 'pc'] as $item) {
            $module_dir = $this->root_path . 'public/' . $item . DIRECTORY_SEPARATOR;
            if (is_dir($module_dir)) {
                if (!is_dir($backup_dir . $item)) File::mk_dir($backup_dir . $item, 0755);
                File::copy_dir($module_dir, $backup_dir . $item);
            }
        }

        $package_dir = $backup_dir . 'package' . DIRECTORY_SEPARATOR;
        if(!is_dir($package_dir)) File::mk_dir($package_dir, 0755);

        // 备份 composer package pages 原文件，只备份原始文件
        if(!file_exists($package_dir . 'composer.json')) {
            copy($this->root_path . 'composer.json', $package_dir . 'composer.json');
        }

        if(!file_exists($package_dir . 'admin_package.json')) {
            copy($this->sys_path . 'admin/package.json', $package_dir . 'admin_package.json');
        }

        if(!file_exists($package_dir . 'uniapp_package.json')) {
            copy($this->sys_path . 'uniapp/package.json', $package_dir . 'uniapp_package.json');
        }

        if(!file_exists($package_dir . 'pc_package.json')) {
            copy($this->sys_path . 'pc/package.json', $package_dir . 'pc_package.json');
        }

        if(!file_exists($package_dir . 'uniapp_pages.json')) {
            copy($this->sys_path . 'uniapp/src/pages.json', $package_dir. 'uniapp_pages.json');
        }

        return true;
    }

    /**
     * 安装sql
     * @return true
     */
    protected function executeSql()
    {
        (new Sql)->executeSql($this->download_path . $this->addon_key . '/server/sql/install.sql');
        return true;
    }

     /**
     * 获取插件菜单父ID
     * @return int
     */
    private function getMenuPid() {
        $row = Db::name('system_menu')->where(['paths' => '/addons', 'type' => 'M'])->findOrEmpty();
        if(empty($row['id'])) {
            $time = time();
            $add_id = Db::name('system_menu')->insert([
                'pid' => 0,
                'type' => 'M',
                'name' => '插件',
                'icon' => 'ele-Grid',
                'sort' => 250,
                'perms' => '',
                'paths' => '/addons',
                'component' => '',
                'selected' => '',
                'params' => '',
                'is_keep_alive' => 0,
                'is_link' => 0,
                'link_url' => '',
                'is_iframe' => 0,
                'is_hide' => 0,
                'is_affix' => 0,
                'is_disable' => 0,
                'addon' => '',
                'create_time' => $time,
                'update_time' => $time,
            ], true);
            return $add_id;
        } else {
            return $row['id'];
        }
    }

    /**
     * 添加菜单
     * @param $menu_arr
     * @param $pid
     * @return true
     */
    public function addMenus($menu_arr, $pid = 0)
    {
        if($menu_arr && is_array($menu_arr)) {
            $time = time();
            foreach ($menu_arr as $item) {
                $add_id = Db::name('system_menu')->insert([
                    'pid' => empty($item['pid'])?$pid:$item['pid'],
                    'type' => $item['type'],
                    'name' => $item['name'],
                    'icon' => $item['icon'] ?? '',
                    'sort' => $item['sort'] ?? 0,
                    'perms' => $item['perms'] ?? '',
                    'paths' => $item['paths'] ?? '',
                    'component' => $item['component'] ?? '',
                    'selected' => $item['selected'] ?? '',
                    'params' => $item['params'] ?? '',
                    'is_keep_alive' => $item['is_keep_alive'] ?? 0,
                    'is_link' => $item['is_link'] ?? 0,
                    'link_url' => $item['link_url'] ?? '',
                    'is_iframe' => $item['is_iframe'] ?? 0,
                    'is_hide' => $item['is_hide'] ?? 0,
                    'is_affix' => $item['is_affix'] ?? 0,
                    'is_disable' => $item['is_disable'] ?? 0,
                    'addon' => $this->addon_key,
                    'create_time' => $time,
                    'update_time' => $time,
                ], true);
                if(!empty($item['children'])) {
                    $this->addMenus($item['children'], $add_id);
                }
            }
        }

        return true;
    }

    /**
     * 添加计划任务
     * @param $crontab_arr
     * @return true
     */
    public function addCrontab($crontab_arr)
    {
        if($crontab_arr && is_array($crontab_arr)) {
            $time = time();
            foreach ($crontab_arr as $item) {
                 Db::name('sys_crontab')->insert(
                     [
                         'name' => $item['name'],
                         'command' => $item['command'],
                         'params' => $item['params'] ?? '',
                         'expression' => $item['expression'] ?? '',
                         'expression_params' => $item['expression_params'] ?? '',
                         'remark' => $item['remark'] ?? '',
                         'status' => $item['status'] ?? 1,
                         'addon' => $this->addon_key,
                         'create_time' => $time,
                         'update_time' => $time,
                     ]
                 );
            }
        }

        return true;
    }

    /**
     * 删除菜单
     * @return void
     * @throws \think\db\exception\DbException
     */
    public function delMenus() {
        if($this->addon_key)Db::name('system_menu')->where(['addon' => $this->addon_key])->delete();
    }

    /**
     * 删除计划任务
     * @return void
     * @throws \think\db\exception\DbException
     */
    public function delCrontab() {
        if($this->addon_key)Db::name('sys_crontab')->where(['addon' => $this->addon_key])->delete();
    }

    /**
     * 安装异常
     * @return true
     * @throws Exception
     */
    protected function installExceptionHandle()
    {
        $addon_admin_dir = $this->sys_path . 'admin/src/addons/' . $this->addon_key . DIRECTORY_SEPARATOR;
        $addon_app_dir = $this->sys_path . 'uniapp/src/addons/' . $this->addon_key . DIRECTORY_SEPARATOR;
        $addon_pc_dir = $this->sys_path . 'pc/addons/' . $this->addon_key . DIRECTORY_SEPARATOR;
        $addon_server_dir = $this->addon_path . $this->addon_key . DIRECTORY_SEPARATOR;
        $addon_resource_dir = $this->root_path . 'public/addons/' . $this->addon_key . DIRECTORY_SEPARATOR;

        // 卸载管理端
        if (is_dir($addon_admin_dir)) File::del_dir($addon_admin_dir);
        // 卸载手机端
        if (is_dir($addon_app_dir)) File::del_dir($addon_app_dir);
        // 卸载电脑端
        if (is_dir($addon_pc_dir)) File::del_dir($addon_pc_dir);
        // 卸载管理端
        if (is_dir($addon_server_dir)) File::del_dir($addon_server_dir);
        // 卸载资源文件
        if (is_dir($addon_resource_dir)) File::del_dir($addon_resource_dir);

        $backup_dir = $this->root_path . 'data/backup' . DIRECTORY_SEPARATOR;

        // 还原 composer package pages 文件
        if(is_file($backup_dir . 'package/composer.json')) {
            copy($backup_dir . 'package/composer.json', $this->root_path . 'composer.json');
        }

        if(is_file($backup_dir . 'package/admin_package.json')) {
            copy($backup_dir . 'package/admin_package.json', $this->sys_path . 'admin/package.json');
        }

        if(is_file($backup_dir . 'package/uniapp_package.json')) {
            copy($backup_dir . 'package/uniapp_package.json', $this->sys_path . 'uniapp/package.json');
        }

        if(is_file($backup_dir . 'package/pc_package.json')) {
            copy($backup_dir . 'package/pc_package.json', $this->sys_path . 'pc/package.json');
        }

        if(is_file($backup_dir . 'package/uniapp_pages.json')) {
            copy($backup_dir . 'package/uniapp_pages.json', $this->sys_path . 'uniapp/src/pages.json');
        }

        // 删除备份文件
        if (is_dir($backup_dir)) File::del_dir($backup_dir);

        // 执行卸载sql
        (new Sql)->executeSql($this->download_path . $this->addon_key . '/server/sql/uninstall.sql');

        // 删除菜单
        $this->delMenus();

        // 删除计划任务
        $this->delCrontab();

        // 删除插件配置信息
        (new ModelService)->delAddonInfo($this->addon_key);

        // 刷新插件缓存
        UtilService::refreshInstallAddons();

        return true;
    }


    /**
     * 安装执行
     * @return true
     * @throws Exception
     */
    protected function installExecute()
    {
        // 获取方法
        $class = 'addons\\' . $this->addon_key . '\\' . 'Addon';
        if(!class_exists($class)) throw new Exception('插件类加载失败');
        $methods = (array)get_class_methods($class);

        // 安装sql
        $this->executeSql();

        $addonClass = (new $class());

        // 安装菜单
        if(in_array('menu', $methods)) {
            $menu_arr = $addonClass->menu();
            if($menu_arr) {
                $this->addMenus($menu_arr, $this->getMenuPid());
                // 管理端刷新菜单缓存
                (new AdminAuthCache)->checkAuthChange();
            }
        }

        // 安装计划任务
        if(in_array('crontab', $methods)) {
            $crontab_arr = $addonClass->crontab();
            if($crontab_arr) {
                $this->addCrontab($crontab_arr);
            }
        }

        // 执行install方法
        $addonClass->install();

        // 设置插件缓存
        $addonClass->getInfo();
        $addonClass->getConfig();
        $addonClass->getEvent();
        $addonClass->getCommand();

        return true;
    }

    /**
     * 添加插件配置信息
     * @return void
     * @throws AddonsException|Exception
     */
    public function addAddonInfo($params, $is_local)
    {
        if($is_local) {
            $add_info = [
                'name' => $params['name'] ?? '',
                'identify' => $this->addon_key,
                'thumb' => $params['thumb']?? '',
                'is_local' => 1,
                'version' => $params['version'],
            ];
            $add_id = (new ModelService)->addAddonInfo($add_info);
            if(!$add_id) throw new Exception('添加插件配置信息失败');
        } else {
            $res = AddonApiService::get_addons_info($this->addon_key);
            if(isset($res['identify'])) {
                $add_id = (new ModelService)->addAddonInfo($res);
                if(!$add_id) throw new Exception('添加插件配置信息失败');
            } else {
                throw new Exception('获取插件信息失败');
            }
        }
        UtilService::refreshInstallAddons();
    }

    /**
     * 安装插件
     * @param $params
     * @param $is_local
     * @return true
     * @throws AddonsException
     * @throws Exception
     */
    public function main($params, $is_local)
    {
        // 插件是否已安装
        if(!$this->checkAddonInstall()) return true;

        // 安装前检验
        $this->check($params);

        try {

            set_time_limit(0);

            // 安装目录
            $this->installDir();

            // 安装依赖
            $this->installDepend();

            // 备份目录
            $this->backupDir();

            // 安装执行
            $this->installExecute();

            // 添加插件配置信息
            $this->addAddonInfo($params, $is_local);

            return true;

        } catch (\Exception $e) {
            $this->installExceptionHandle();
            throw new AddonsException($e->getMessage());
        }
    }
}